﻿using System;
using System.Threading;
using System.Reflection;
using System.Collections.Generic;
using Net.Event;
using Net.Helper;
using Net.System;

namespace Net.Share
{
    /// <summary>
    /// 添加Rpc模式
    /// </summary>
    [Flags]
    public enum AddRpcMode
    {
        /// <summary>
        /// 啥都不干
        /// </summary>
        None = 0,
        /// <summary>
        /// 追加Rpc, 一个对象可以多次收集Rpc回调
        /// </summary>
        Append = 1 << 0,
        /// <summary>
        /// 获取父类的元数据(字段,属性,方法), 如果父类有Rpc可以使用这个模式
        /// </summary>
        BaseType = 1 << 1,
    }

    /// <summary>
    /// Rpc方法列表
    /// </summary>
    public class RpcMethodList
    {
        public MyDictionary<object, IRpcMethod> Methods = new();
        public SafeDictionary<uint, RpcModelAsync> Requests = new();
        public int Count => Methods.Count;

        /// <summary>
        /// 添加收集的方法
        /// </summary>
        /// <param name="target">收集的对象实例</param>
        /// <param name="value">收集对象的方法</param>
        public void Add(object target, IRpcMethod value)
        {
            Methods.Add(target, value);
        }

        /// <summary>
        /// 移除收集对象的方法
        /// </summary>
        /// <param name="target"></param>
        public void Remove(object target)
        {
            Methods.Remove(target);
        }
    }

    /// <summary>
    /// Rpc适配器基类
    /// </summary>
    public abstract class RpcAdapterBase : IRpcAdapter
    {
        //在服务器项目, 如果每个客户端都有收集Rpc特性, 会导致每次都要反射调用源数据, 这样会影响性能, 所以这里定义了静态类型, 所有的客户端都使用这个元数据就行
        private static readonly MyDictionary<Type, List<MemberData>> memberInfos = new();
        private readonly MyDictionary<object, MemberDataList> rpcTargetHash = new();

        public bool LogRpc { get; set; }
        public MyDictionary<uint, RpcMethodList> RpcMethods { get; set; } = new();
        public MyDictionary<ushort, SyncVarInfo> SyncVarDic { get; set; } = new();
        public JobQueueHelper WorkerQueue { get; set; } = new();
        public AdapterMode Mode { get; set; }

        /// <summary>
        /// 添加Rpc
        /// </summary>
        /// <param name="target">注册的对象实例</param>
        /// <param name="mode">一个Rpc方法是否可以多次添加到Rpcs里面？</param>
        /// <param name="onSyncVarCollect"></param>
        public virtual void AddRpc(object target, AddRpcMode mode = AddRpcMode.None, Action<SyncVarInfo> onSyncVarCollect = null)
            => AddRpc(target, target.GetType(), mode, onSyncVarCollect);

        public virtual void AddRpc(object target, Type type, AddRpcMode mode, Action<SyncVarInfo> onSyncVarCollect)
            => AddRpc(target, type, mode, onSyncVarCollect, null, (member) => new RpcMethod(target, member.member as MethodInfo, member.rpc.cmd));

        public virtual void AddRpc(object target, Type type, AddRpcMode mode, Action<SyncVarInfo> onSyncVarCollect, Action<MemberInfo, MemberData> action, Func<MemberData, IRpcMethod> func)
        {
            if (!mode.HasFlag(AddRpcMode.Append))
                if (rpcTargetHash.ContainsKey(target))
                    return;
            Monitor.Enter(memberInfos); //在服务器有并行可能, 需要加锁
            if (!memberInfos.TryGetValue(type, out var memberDatas))
            {
                memberDatas = new();
                var members = new List<MemberInfo>();
                Type baseType = type;
                do
                {
                    members.AddRange(baseType.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); //只获取当前类型元数据
                    baseType = baseType.BaseType;
                }
                while (baseType != null && mode.HasFlag(AddRpcMode.BaseType)); //使用sealed class定义的类, 获取不到子[Rpc]或者其他原因导致的获取基类失败, 所以需要一层一层获取
                foreach (var info in members)
                {
                    var data = new MemberData() { member = info };
                    if (info.MemberType == MemberTypes.Method)
                    {
                        var attributes = info.GetCustomAttributes(typeof(Rpc), true);//兼容ILR写法
                        if (attributes.Length > 0)
                        {
                            data.rpc = attributes[0] as Rpc;
                            action?.Invoke(info, data);
                        }
                    }
                    else if (info.MemberType == MemberTypes.Field || info.MemberType == MemberTypes.Property)
                    {
                        SyncVarHelper.InitSyncVar(info, target, (syncVar) => data.syncVar = syncVar);
                    }
                    if (data.rpc != null || data.syncVar != null)
                        memberDatas.Add(data);
                }
                memberInfos.Add(type, memberDatas);
            }
            Monitor.Exit(memberInfos);
            foreach (var member in memberDatas)
            {
                var rpc = member.rpc;
                if (rpc != null)
                {
                    var item = func(member);
                    if (string.IsNullOrEmpty(rpc.func))
                        rpc.func = item.method.Name;
                    if (!RpcMethods.TryGetValue(rpc.func.CRCU32(), out var body))
                        RpcMethods.Add(rpc.func.CRCU32(), body = new RpcMethodList());
                    if (rpc.hash != 0)
                        RpcMethods.TryAdd(rpc.hash, body);
                    body.Add(target, item);
                }
                var syncVar = member.syncVar;
                if (syncVar != null)
                {
                    var syncVar1 = syncVar.Clone(target);
                    if (syncVar1.id == 0)
                        onSyncVarCollect?.Invoke(syncVar1);
                    else
                        SyncVarDic.TryAdd(syncVar1.id, syncVar1);
                }
            }
            if (memberDatas.Count > 0)
                rpcTargetHash.Add(target, new MemberDataList() { members = memberDatas });
        }

        public void RemoveRpc(object target)
        {
            if (rpcTargetHash.TryRemove(target, out var list))
            {
                foreach (var item in list.members)
                {
                    if (item.rpc != null)
                    {
                        if (RpcMethods.TryGetValue(item.rpc.hash, out var dict))
                            dict.Remove(target);
                        if (RpcMethods.TryGetValue(item.rpc.func.CRCU32(), out dict))
                            dict.Remove(target);
                    }
                    if (item.syncVar != null)
                        SyncVarDic.Remove(item.syncVar.id);
                }
            }
        }

        public void Enqueue(object state, IRpcModel model)
        {
            if (!RpcMethods.TryGetValue(model.Protocol, out var body))
            {
                RpcLog(0, state, model);
                return;
            }
            if (model.Token == 0 && body.Requests.Count > 0)
            {
                RpcLog(1, state, model);
                return;
            }
            if (model.Token != 0)
            {
                if (body.Requests.TryRemove(model.Token, out var modelTask))
                {
                    modelTask.SetCompleted(model);
                    if (modelTask.Intercept)
                        return;
                }
                //如果超时了会执行到这里，因为开发者是以Request发起的，所以大概率不会再定义一个[Rpc]来二次调用! 如果你二次调用也是没有问题，但是没有二次调用时，我们不应该提示添加[Rpc]
                //如果你设置intercept=true并且[Rpc]方法存在才会往下执行，要不然就直接返回，也不需要提示添加[Rpc]警告，因为这个不是错误问题
                if (body.Count <= 0)
                    return;
            }
            else if (body.Count <= 0)
            {
                RpcLog(2, state, model);
                return;
            }
            InvokeRpc(body.Methods, state, model);
        }

        protected virtual void RpcLog(int log, object state, IRpcModel model)
        {
            switch (log)
            {
                case 0:
                    NDebug.LogWarning($"{state} [protocol:{model.Protocol}]的远程方法未被收集!请定义[Rpc(hash = {model.Protocol})] void xx方法和参数, 并使用client.AddRpc方法收集rpc方法!");
                    break;
                case 1:
                    NDebug.LogWarning($"{state} [protocol={model.Protocol}]服务器响应的Token={model.Token}没有进行设置! 服务器请使用Response进行回应, 并且传入client.Token参数! 不要用Call回应! (注意:如果异步需要在异步前写上var token = client.Token; 然后在Response传入了token参数)");
                    break;
                case 2:
                    NDebug.LogWarning($"{state} {model}的远程方法未被收集!请定义[Rpc]void xx方法和参数, 并使用client.AddRpc方法收集rpc方法!");
                    break;
            }
        }

        protected virtual void InvokeRpc(MyDictionary<object, IRpcMethod> methods, object state, IRpcModel model)
        {
            foreach (var method in methods.Values)
            {
                method.Invoke(model.Pars);
            }
        }

        public void Execute() => WorkerQueue.Execute();

        public RpcMethodList GetOrAddRpcMethodList(uint protocol)
        {
            if (!RpcMethods.TryGetValue(protocol, out RpcMethodList body))
                RpcMethods.Add(protocol, body = new RpcMethodList());
            return body;
        }

        protected void InvokeSafeMethod(object state, IRpcMethod method, object[] pars)
        {
            var len = pars.Length;
            var array = new object[len + 1];
            array[0] = state;
            Array.Copy(pars, 0, array, 1, len);
            InvokeMethod(method, array);
        }

        public void InvokeMethod(IRpcMethod method, object[] pars)
        {
            try
            {
                if (LogRpc)
                {
                    if (!ScriptHelper.Cache.TryGetValue(method.target.GetType().FullName + "." + method.method.Name, out var sequence))
                        sequence = new SequencePoint();
                    NDebug.Log($"RPC:{method} () (at {sequence.FilePath}:{sequence.StartLine}) \n");
                }
                if (method == null)
                    return;
                method.Invoke(pars);
            }
            catch (Exception ex)
            {
#if UNITY_EDITOR
                if (!ScriptHelper.Cache.TryGetValue(method.target.GetType().FullName + "." + method.method.Name, out var sequence))
                    sequence = new SequencePoint();
                var info = $"{method.method.Name}方法内部发生错误!\n() (at {sequence.FilePath}:{sequence.StartLine}) \n";
                var reg = new global::System.Text.RegularExpressions.Regex(@"\)\s\[0x[0-9,a-f]*\]\sin\s(.*:[0-9]*)\s");
                info += reg.Replace(ex.ToString(), ") (at $1) ");
                var dataPath = PathHelper.PlatformReplace(UnityEngine.Application.dataPath).Replace("Assets", "");
                info = PathHelper.PlatformReplace(info.Replace(dataPath, ""));
                NDebug.LogError(info);
#else
                NDebug.LogError($"{method.method.Name}方法内部发生错误! 详细信息:" + ex);
#endif
            }
        }

        /// <summary>
        /// 复制新的适配器
        /// </summary>
        /// <returns></returns>
        public abstract IRpcAdapter Copy();
    }

    /// <summary>
    /// 普通Rpc适配器
    /// </summary>
    public class NormalRpcAdapter : RpcAdapterBase
    {
        public override IRpcAdapter Copy()
        {
            return new NormalRpcAdapter() { LogRpc = LogRpc, Mode = Mode };
        }

        protected override void InvokeRpc(MyDictionary<object, IRpcMethod> methods, object state, IRpcModel model)
        {
            foreach (var method in methods.Values)
            {
                switch (method.cmd)
                {
                    case NetCmd.SafeCallHash:
                        InvokeSafeMethod(state, method, model.Pars);
                        break;
                    case NetCmd.SingleCallHash:
                        ThreadManager.Event.AddEvent(0f, (pars) => InvokeSafeMethod(state, method, pars), model.Pars);
                        break;
                    case NetCmd.SafeCallAsyncHash:
                        var workCallback = new RpcWorkParameter(state, method, model.Pars);
                        System.ThreadPool.UnsafeQueueUserWorkItem(workCallback.RpcWorkCallback, workCallback);
                        break;
                    case NetCmd.ThreadRpcHash:
                        InvokeMethod(method, model.Pars);
                        break;
                    default:
                        switch (Mode)
                        {
                            case AdapterMode.Server:
                                InvokeMethod(method, model.Pars);
                                break;
                            case AdapterMode.Client:
                                WorkerQueue.Call(new RpcInvokeArgs(LogRpc, method, model.Pars));
                                break;
                            case AdapterMode.ClientDirect:
                                InvokeMethod(method, model.Pars);
                                break;
                            case AdapterMode.None:
                                NDebug.LogError($"请正确设置RpcAdapter适配器, Mode属性必须指定是Server或者Client!");
                                break;
                        }
                        break;
                }
            }
        }
    }
}